package domainedvd;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import java.util.TreeSet;

import supervise.ClassifieurMultinomial;
import utils.Review;

/**
 * Un classifieur pour les DVD se basant sur la proximite des autres avis
 *
 */
public class DVDProximite extends ClassifieurMultinomial {
	
	private HashMap<String, Double> betaDVD;
	private ArrayList<Review> reviewsProches;
	
	/**
	 * Crée un classifieur pour un domaine se basant sur la
	 * proximite d'autres avis pour choisir les meilleures
	 * et leur appliquer un apprentissage supervisé
	 * @param DVDReviews Les reviews du domaine à classifier
	 * @param autreReviews Les review étiquetées d'autres domaines
	 * @param parametreLissage Paramètre de lissage de Laplace
	 * @param pourcentageGarde pourcentage des reviews d'autres domaines à garder 
	 */
	public DVDProximite(ArrayList<Review> DVDReviews, ArrayList<Review> autreReviews,
			double parametreLissage, double pourcentageGarde) {
		// construction du dico
//		System.out.println("Dico...");
		construireDictionnaire(DVDReviews, null, 2.d);
		// apprendissage du classifieur M
//		System.out.println("Appr DVD...");
		apprendreDVD(DVDReviews, parametreLissage);
		// filtrage des documents de supervision
//		System.out.println("Proximite...");
		reviewsProches = getNearestReviews(autreReviews, pourcentageGarde);
//		reviewsProches = autreReviews;
		// apprentissage multinomial classique sur les données de supervision
		// refaisage du dictionnaire
		construireDictionnaire(reviewsProches, null, 2.d);
//		System.out.println("Apprentissage normal...");
		apprendre(reviewsProches, parametreLissage);
		
	}
	
	/**
	 * Remplit les béta pour une seule classe :
	 * celle à laquelle appartiennent toutes les reviews données
	 * @param reviews Les reviews du domaine dont on veut faire une classe
	 * @param parametreLissage Le paramètre de lissage de Laplace
	 */
	private void apprendreDVD(ArrayList<Review> reviews, double parametreLissage) {
		betaDVD = new HashMap<String, Double>();
		String token;
		int totalMots = 0;
		double lisse;

		// On met tout le dictionnaire dans le vecteur
		for (String mot : dictionnaire) {
			betaDVD.put(mot, 0.d);
		}

		// Pour chaque review
		for (Review review : reviews) {
			StringTokenizer tokenizer = new StringTokenizer(review.getTexte(),
					" ");
			// Pour chaque mot
			while (tokenizer.hasMoreTokens()) {
				token = tokenizer.nextToken().toLowerCase();
				// S'il appartient au dico
				if (dictionnaire.contains(token)) {
					// On ajoute 1 dans le compte negatif
					betaDVD.put(token, betaDVD.get(token) + 1);
					totalMots++;
				}
			}// tous les tokens
		}// toutes les review

		// On normalise
//		for (String mot : betaNegatif.keySet()) {
//			norma = betaNegatif.get(mot) * totalPositif / totalNegatif;
//			betaNegatif.put(mot, norma);
//		}

		// Laplace
		for (String mot : betaDVD.keySet()) {
			lisse = (betaDVD.get(mot) + parametreLissage)
					/ (totalMots + parametreLissage);
			betaDVD.put(mot, lisse);
		}
	}

	/**
	 * Retourne la liste des reviews les plus proches du domaine
	 * qu'on étudie
	 * @param reviews La liste des reviews a trier
	 * @param pourcentGardes Le pourcentage de reviews à garder
	 * @return Les pourcentageGardes % d'avis les plus proches
	 * 			du domaine étudié
	 */
	private ArrayList<Review> getNearestReviews(
			ArrayList<Review> reviews, double pourcentGardes) {
		ArrayList<Review> res = new ArrayList<Review>();
		double vrais = 0;
		ArrayList<Double> probasTri = new ArrayList<Double>();
		int i=0;
		// Pour toutes les reviews
		for(Review review: reviews) {
			// On calcule sa logVraisemblance d'appartenir au domaine
			vrais = getLogVraisemblance(review);
			i = -1;
			// On cherche sa place dans le tableau des vraisemblances triées
			while(++i < probasTri.size()
					&& vrais < probasTri.get(i));
			if(i >= probasTri.size()) {
				probasTri.add(vrais);
			}
			else {
				probasTri.add(i, vrais);
			}
			review.setProba(vrais);
		}		
		
		// On ne garde que les pourcentgardes % premiers
		int nbGardes =  (int)(probasTri.size()*pourcentGardes/100.d);
		Double max = probasTri.get(nbGardes-1);
//		System.out.println("Je garde les " + nbGardes + " au dessus de " + max);
		
		// Pour toutes les reviews
		for(Review review: reviews) {
			// Si la review est plus proche que max
			// et si on a pas atteint les pc % premiers
			if(review.getProba() >= max
					&& res.size() < nbGardes) {
				// On l'ajoute au resultat
				res.add(review);
			}
			review.setProba(0.d);
		}
//		System.out.println("Je renvoie " + res.size() + " reviews");
		return res;
	}
	
	/**
	 * Retourne la logvraisemblance d'une review
	 * par rapport au domaine étudié
	 * @param review La Review dont on veut calculer la logvraisemblance
	 * @return
	 */
	private double getLogVraisemblance(Review review) {
		StringTokenizer tokenizer;
		String token;
		HashMap<String, Double> compteReview = new HashMap<String, Double>();
		double occurrences;
		double proba;

		tokenizer = new StringTokenizer(review.getTexte());
		// Pour chaque mot
		while (tokenizer.hasMoreTokens()) {
			token = tokenizer.nextToken().toLowerCase();
			occurrences = 0.d;
			// S'il est dans le dico
			if (dictionnaire.contains(token)) {
				// Si on l'a deja vu
				if (compteReview.containsKey(token)) {
					// On recupere son nombre d'apparitions
					occurrences = compteReview.get(token);
				}
				// On le met dans le compte avec nbOcc +1
				compteReview.put(token, occurrences + 1);
			}
		} // tous les tokens

		// On calcule sa proba d'être une critique DVD
		proba = 0.d;
		// Pour chaque mot du document
		for (String mot : compteReview.keySet()) {
			proba += Math.log(betaDVD.get(mot))
					* compteReview.get(mot);
		}
		return proba;
	}
	
	public ClassifieurMultinomial getClassifieurMultinomial() {
		return new ClassifieurMultinomial(reviewsProches, null, 2.d, 1.d);
	}
			
}
